import { Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
import * as Minio from 'minio';
import { Readable } from 'stream';
import { ResponseMsgKey } from '../../../common/constants/api-response.constant';
import { PUBLIC_FILE_DIRECTORY } from '../../../common/constants/minio.constant';

@Injectable()
export class MinioService {
  private readonly bucket: string;
  private readonly logger = new Logger(MinioService.name);
  constructor(
    @Inject('MINIO_CLIENT') private readonly minio: Minio.Client,
    private readonly configService: ConfigService,
  ) {
    this.bucket = this.configService.get('MINIO_BUCKET');
  }

  async getBuckets() {
    return await this.minio.listBuckets();
  }

  /**
   * 上传文件
   * @param fileName 文件名
   * @param buffer 文件流
   * @param bucket 桶名
   */
  async uploadFile(
    fileName: string,
    buffer: Buffer,
    bucket: string = this.bucket,
  ) {
    return await this.minio.putObject(bucket, fileName, buffer);
  }

  /**
   * 获取文件流
   * @param fileName 文件名
   * @returns 文件流
   */
  async getFileBuffer(fileName: string): Promise<Readable> {
    try {
      return await this.minio.getObject(this.bucket, fileName);
    } catch (error) {
      this.logger.error(JSON.stringify(error));
      throw new Error(ResponseMsgKey.FILE_NOT_FOUND);
    }
  }

  /**
   * 批量删除文件
   * @param fileNames 文件名列表
   * @param bucket 桶名
   * @returns
   */
  async deleteFiles(fileNames: string[], bucket: string = this.bucket) {
    return await this.minio.removeObjects(bucket, fileNames);
  }

  /**
   * 获取上传minio文件链接（使用put请求链接，将待上传的文件放置body.binary）
   * @param fileName 文件名
   * @param bucketName 桶名
   * @param expiry 链接有效时间 /s
   * @returns url
   */
  async presignedPutUrl(
    fileName: string,
    bucketName: string = this.bucket,
    expiry: number = 24 * 60 * 60,
  ) {
    return await this.minio.presignedPutObject(bucketName, fileName, expiry);
  }

  /**
   * 获取下载minio文件链接（使用get请求链接）
   * @param fileName 文件名
   * @param bucketName 桶名
   * @param expiry 链接有效时间 /s
   * @returns url
   */
  async presignedGetUrl(
    fileName: string,
    bucketName: string = this.bucket,
    expiry: number = 24 * 60 * 60,
  ) {
    return await this.minio.presignedGetObject(bucketName, fileName, expiry);
  }

  /**
   * 获取上传minio文件链接（使用post请求链接）
   * 请求 postURL 链接
   * 携带 formData，待上传文件放置 "file" 字段
   * @param fileName 文件名
   * @param bucketName 桶名
   * @param expiry 链接有效时间 /s
   * @returns
   */
  async presignedPostPolicy(
    fileName: string,
    bucketName: string = this.bucket,
    expiry: number = 24 * 60 * 60,
  ) {
    const policy = new Minio.PostPolicy();
    policy.setBucket(bucketName);
    policy.setKey(fileName);
    policy.setExpires(new Date(new Date().getTime() + expiry * 1000));
    return await this.minio.presignedPostPolicy(policy);
  }
}

/**
 * 根据文件计算MD5生成文件名
 * @param buffer
 * @param fileExtension 文件扩展名
 * @returns
 */
export const generateFileName = (buffer: Buffer, fileExtension: string) => {
  const hash = crypto.createHash('md5');
  hash.update(buffer);
  return `${hash.digest('hex')}${fileExtension}`;
};

/**
 * 获取开放文件下载链接
 * @param fileName
 * @returns
 */
export const getPublicFileLink = (fileName: string) => {
  fileName = fileName?.startsWith('/') ? fileName.substring(1) : fileName;
  return fileName?.startsWith(PUBLIC_FILE_DIRECTORY)
    ? process.env.MINIO_SERVER + fileName
    : null;
};
